package study.translation.parser;

import study.translation.grammar.Grammar;
import study.translation.grammar.Nonterminal;
import study.translation.grammar.ParseException;
import study.translation.grammar.Rule;
import study.translation.grammar.Symbol;
import study.translation.grammar.Terminal;

import java.io.InputStream;
import java.util.List;

public class ComplexParser
{
    private static final String START_SYMBOL = "program";

    private Lexer lexer;

    private Nonterminal startSymbol;

    public ComplexParser( Grammar grammar )
    {
        startSymbol = grammar.getNonterminal( START_SYMBOL );
    }

    public ParseTree parse( InputStream inputStream ) throws ParseException
    {
        lexer = new Lexer( inputStream );
        ParseTree.InnerNode root = parse( startSymbol );
        if( ( null == root ) || !lexer.atEnd() )
        {
            throw new ParseException( "Invalid syntax near \"" + lexer.getCodePart()
                                      + "\" at line " + lexer.getLineNumber() );
        }
        return new ParseTree( root );
    }

    private ParseTree.InnerNode parse( Nonterminal n ) throws ParseException
    {
        boolean read = false;
        ParseTree.InnerNode result = null;
        for( Rule rule : n.getRules() )
        {
            if( !rule.isEmpty() )
            {
                boolean parsed = true;
                List<Symbol> rightPart = rule.getRightPart();
                ParseTree.NodeList childNodes = new ParseTree.NodeList( rightPart.size() );
                for( Symbol symbol : rightPart )
                {
                    if( symbol instanceof Nonterminal )
                    {
                        Nonterminal next = ( Nonterminal )symbol;
                        ParseTree.Node node = parse( next );
                        if( null != node )
                        {
                            childNodes.add( node );
                        }
                        else
                        {
                            parsed = false;
                        }
                    }
                    else
                    {
                        Lexem.Type type = Lexem.Type.byName( symbol.getName() );
                        if( null == type )
                        {
                            throw new ParseException( "Unexpected terminal symbol: " + symbol );
                        }
                        Lexem lexem = lexer.readLexem( type );
                        if( null != lexem )
                        {
                            childNodes.add( new ParseTree.LeafNode( ( Terminal )symbol, lexem ) );
                            read = true;
                        }
                        else
                        {
                            parsed = false;
                        }
                    }
                    if( !parsed )
                    {
                        break; // continue to next rule
                    }
                }
                if( parsed )
                {
                    // rule was applied
                    result = new ParseTree.InnerNode( n, childNodes );
                    break;
                }
            }
        }
        if( ( null == result ) && !read && n.hasEmptyRule() )
        {
            result = new ParseTree.InnerNode( n );
        }
        return result;
    }
}
